API GatewayとLambdaのデプロイ中にAPIアクセスしてみた
ふと気になりました。「Lambdaのデプロイ中にAPI経由でアクセスすると、どういう動きになるんだろう?」と。
- ある時刻を境にバッチリ変わる?
- デプロイ付近で新しいLambdaと古いLambdaがランダムに実行される?
試してみました。
おすすめの方
- AWS SAMを使ってみたい方
- API GatewayとLambdaでAPIを作ってみたい方
- LambdaとDynamoDBを使ってみたい方
- API GatewayとLambdaのデプロイ中にアクセスした場合の挙動に興味がある方
ざっくり概要
APIを1つ作成し、同時アクセスします。APIの裏にいるLambdaにて受け取ったデータをDynamoDBに書き込みます。 APIアクセスを続けている状態でLambdaをデプロイ(コード変更)したとき、どのような動きになるのか?を試してみました。
まずはWebAPIを作成する
SAM Init
sam init \ --runtime python3.7 \ --name api-lambda-deploy-sample \ --app-template hello-world
SAMテンプレートファイル
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: api-lambda-deploy-sample-stack Parameters: Number: Type: String Resources: TodoFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.7 Timeout: 10 AutoPublishAlias: Sample Environment: Variables: TABLE_NAME: !Ref TodoTable Policies: - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess Events: HelloWorld: Type: Api Properties: Path: /todo Method: post TodoFunctionLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${TodoFunction} TodoTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub todo-sample-${Number}-table AttributeDefinitions: - AttributeName: todoId AttributeType: S KeySchema: - AttributeName: todoId KeyType: HASH BillingMode: PAY_PER_REQUEST Outputs: ApiEndpoint: Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/todo"
Lambdaコード
DynamoDBに書き込むデータにlambda:aaa
を含めています。新しいLambdaではこの内容を変更するため、デプロイ前後どちらのLambdaが書き込んだのかを区別できるようにしています。
import os import json import boto3 from datetime import datetime, timezone dynamodb = boto3.resource('dynamodb') table_name = os.environ['TABLE_NAME'] def lambda_handler(event, context): payload = json.loads(event['body']) table = dynamodb.Table(table_name) try: table.put_item(Item={ 'todoId': payload['todoId'], 'title': payload['title'], 'createdAt': int(datetime.now(timezone.utc).timestamp() * 1000), 'lambda': 'aaa' }) except Exception as e: print(e) return { "statusCode": 500, "body": json.dumps({ "message": "ng", }) } return { "statusCode": 200, "body": json.dumps({ "message": "ok", }) }
AWS SAM デプロイ
下記でデプロイします。1
としている部分の数字を変更することで、スタック名を変えて同じ環境を構築できるようにしています。
sam build sam package \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-sam-test-bucket sam deploy \ --template-file packaged.yaml \ --stack-name api-lambda-deploy-sample-1-stack \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset \ --parameter-overrides Number=1
APIエンドポイントを取得
下記コマンドでAPIエンドポイントを取得し、メモしておきます。
aws cloudformation describe-stacks \ --stack-name api-lambda-deploy-sample-1-stack \ --query 'Stacks[].Outputs'
APIアクセスし続けるスクリプトを作成
下記のPythonスクリプトを作成します。同ディレクトリにfinish.txt
が置かれるまで、APIアクセスを続けます。
API_ENDPOINT
にはAPIエンドポイントを記載します。
import json import os import time import sys import requests # programme finish trigger file FINISH_FILE = 'finish.txt' API_ENDPOINT = 'https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/todo' def post(index): count = 1 result_ok = 0 result_ng = 0 while True: payload = get_payload(index, count) resp = requests.post(API_ENDPOINT, data=json.dumps(payload)) if resp.status_code == 200: result_ok += 1 else: result_ng += 1 if is_finish(): break time.sleep(0.3) count += 1 print(f'ok: {result_ok}') print(f'ng: {result_ng}') def get_payload(index, count): return { 'todoId': f't{index:02}-{count:04}', 'title': 'アレを買う' } def is_finish(): if os.path.isfile(FINISH_FILE): return True return False if __name__ == "__main__": args = sys.argv if len(args) == 2: post(int(args[1]))
やってみる
APIアクセスを開始する
APIアクセスするスクリプトを並列実行します。
python poster.py 1 & python poster.py 2 & python poster.py 3 &
Lambdaコードを修正する
下記に変更します。DynamoDBに書き込むデータについて、一部をaaa
からxxx
に変更しています。
これによって、デプロイ前後のどちらのLambdaから書き込んだのか区別できます。
import os import json import boto3 from datetime import datetime, timezone dynamodb = boto3.resource('dynamodb') table_name = os.environ['TABLE_NAME'] def lambda_handler(event, context): payload = json.loads(event['body']) table = dynamodb.Table(table_name) try: table.put_item(Item={ 'todoId': payload['todoId'], 'title': payload['title'], 'createdAt': int(datetime.now(timezone.utc).timestamp() * 1000), 'lambda': 'xxx' }) except Exception as e: print(e) return { "statusCode": 500, "body": json.dumps({ "message": "ng", }) } return { "statusCode": 200, "body": json.dumps({ "message": "ok", }) }
デプロイする
sam build sam package \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-sam-test-bucket sam deploy \ --template-file packaged.yaml \ --stack-name api-lambda-deploy-sample-1-stack \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset \ --parameter-overrides Number=1
デプロイ完了後、APIアクセスを停止する
touch finish.txt
結果
- デプロイ付近で新しいLambdaと古いLambdaがランダムに実行される
スクリプト(APIアクセス)の結果
デプロイ途中でも、APIアクセス自体は成功しました。
回数 | ok | ng |
---|---|---|
1回目 | 252 | 0 |
2回目 | 280 | 0 |
3回目 | 274 | 0 |
CloudWatch Logsの様子
複数のLambdaが同時実行されました。
1回目
2回目
3回目
DynamoDBテーブルの様子
DynamoDBテーブルから全データを取得し、時刻順にソートしました。デプロイ時刻付近で新しいLambdaと古いLambdaが混在実行されていることが分かります。
1回目抜粋
↑aaaのみ 1598499496969: t03-0056, aaa 1598499497006: t02-0055, aaa 1598499497117: t01-0056, aaa 1598499497417: t02-0056, aaa 1598499497427: t03-0057, aaa 1598499498051: t01-0057, xxx 1598499498363: t03-0058, aaa 1598499498399: t02-0057, xxx 1598499498694: t01-0058, xxx 1598499499022: t03-0059, aaa 1598499499047: t02-0058, xxx 1598499499119: t01-0059, aaa 1598499499471: t03-0060, xxx 1598499499482: t02-0059, aaa 1598499499579: t01-0060, xxx 1598499499882: t02-0060, xxx 1598499499902: t03-0061, xxx 1598499500008: t01-0061, xxx 1598499500319: t02-0061, xxx 1598499500321: t03-0062, xxx ↓xxxのみ
2回目抜粋
↑aaaのみ 1598519657408: t01-0062, aaa 1598519657743: t02-0063, aaa 1598519657809: t03-0063, aaa 1598519658397: t01-0063, yyy 1598519658701: t02-0064, yyy 1598519658727: t03-0064, yyy 1598519659065: t01-0064, yyy 1598519659375: t02-0065, yyy 1598519659389: t03-0065, yyy 1598519659497: t01-0065, yyy 1598519659811: t02-0066, yyy 1598519659825: t03-0066, yyy 1598519659934: t01-0066, yyy 1598519660249: t02-0067, yyy 1598519660262: t03-0067, yyy 1598519660367: t01-0067, yyy 1598519660666: t02-0068, yyy 1598519660825: t01-0068, yyy 1598519661101: t02-0069, yyy 1598519661217: t03-0068, aaa 1598519661262: t01-0069, yyy 1598519661549: t02-0070, yyy 1598519661715: t01-0070, yyy 1598519661860: t03-0069, yyy 1598519661998: t02-0071, aaa 1598519662138: t01-0071, yyy 1598519662287: t03-0070, yyy 1598519662463: t02-0072, yyy 1598519662566: t01-0072, yyy 1598519662746: t03-0071, yyy 1598519662920: t02-0073, yyy 1598519663000: t01-0073, yyy 1598519663201: t03-0072, yyy 1598519663321: t02-0074, yyy 1598519663427: t01-0074, yyy 1598519663642: t03-0073, yyy 1598519663766: t02-0075, aaa 1598519663866: t01-0075, yyy 1598519664101: t03-0074, yyy 1598519664221: t02-0076, yyy 1598519664323: t01-0076, yyy 1598519664540: t03-0075, yyy 1598519664637: t02-0077, yyy 1598519664759: t01-0077, yyy 1598519664957: t03-0076, aaa 1598519665054: t02-0078, yyy 1598519665210: t01-0078, yyy 1598519665403: t03-0077, yyy 1598519665482: t02-0079, yyy 1598519665643: t01-0079, yyy 1598519665846: t03-0078, yyy 1598519665910: t02-0080, aaa 1598519666080: t01-0080, yyy 1598519666294: t03-0079, yyy 1598519666366: t02-0081, yyy ↓yyyのみ
3回目抜粋
↑aaaのみ 1598519930276: t03-0062, aaa 1598519930390: t02-0063, aaa 1598519931209: t03-0063, zzzzz 1598519931261: t01-0064, zzzzz 1598519931317: t02-0064, aaa 1598519931858: t03-0064, zzzzz 1598519931928: t01-0065, zzzzz 1598519931995: t02-0065, aaa 1598519932322: t03-0065, zzzzz 1598519932358: t01-0066, zzzzz 1598519932425: t02-0066, zzzzz 1598519932764: t03-0066, zzzzz 1598519932815: t01-0067, zzzzz 1598519932873: t02-0067, zzzzz 1598519933195: t03-0067, zzzzz 1598519933280: t01-0068, zzzzz 1598519933411: t02-0068, zzzzz 1598519933651: t03-0068, aaa 1598519933734: t01-0069, zzzzz 1598519933854: t02-0069, zzzzz 1598519934090: t03-0069, zzzzz 1598519934177: t01-0070, zzzzz 1598519934332: t02-0070, zzzzz 1598519934541: t03-0070, zzzzz 1598519934629: t01-0071, zzzzz 1598519934771: t02-0071, aaa 1598519934994: t03-0071, zzzzz 1598519935081: t01-0072, zzzzz 1598519935200: t02-0072, zzzzz 1598519935435: t03-0072, zzzzz 1598519935537: t01-0073, zzzzz 1598519935667: t02-0073, zzzzz 1598519935859: t03-0073, zzzzz 1598519935980: t01-0074, zzzzz 1598519936091: t02-0074, zzzzz 1598519936311: t03-0074, zzzzz 1598519936407: t01-0075, zzzzz 1598519936540: t02-0075, zzzzz 1598519936739: t03-0075, zzzzz 1598519936897: t01-0076, zzzzz 1598519936967: t02-0076, zzzzz 1598519937177: t03-0076, zzzzz 1598519937306: t01-0077, zzzzz 1598519937431: t02-0077, zzzzz 1598519937608: t03-0077, zzzzz 1598519937775: t01-0078, zzzzz 1598519937884: t02-0078, zzzzz 1598519938056: t03-0078, aaa 1598519938194: t01-0079, zzzzz 1598519938354: t02-0079, zzzzz 1598519938511: t03-0079, zzzzz ↓zzzzzのみ
(3回目にして、文字数を変えると分かりやすいことに気づきました……)
さいごに
「デプロイ付近で新しいLambdaと古いLambdaがランダムに実行される」という結果になりました。 だからどうだというわけではないですが、なにかの際に役立つかもしれません。